package com.jingdianjichi.subject.domain.service.impl;

import com.alibaba.fastjson.JSON;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.jingdianjichi.subject.common.enums.IsDeletedFlagEnum;
import com.jingdianjichi.subject.domain.convert.SubjectCategoryConverter;
import com.jingdianjichi.subject.domain.entity.SubjectCategoryBO;
import com.jingdianjichi.subject.domain.entity.SubjectLabelBO;
import com.jingdianjichi.subject.domain.service.SubjectCategoryDomainService;
import com.jingdianjichi.subject.domain.utils.CacheUtil;
import com.jingdianjichi.subject.infra.basic.entity.SubjectCategory;
import com.jingdianjichi.subject.infra.basic.entity.SubjectLabel;
import com.jingdianjichi.subject.infra.basic.entity.SubjectMapping;
import com.jingdianjichi.subject.infra.basic.service.SubjectCategoryService;
import com.jingdianjichi.subject.infra.basic.service.SubjectLabelService;
import com.jingdianjichi.subject.infra.basic.service.SubjectMappingService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;

@Service        //这一层和基础设施层做交互的
@Slf4j
public class SubjectCategoryDomainServiceImpl implements SubjectCategoryDomainService {

    //这里我们是在领域层做操作,当然你controller直接调用也是可以的,我们在封一层
    @Resource
    private SubjectCategoryService categoryService;

    @Resource
    private SubjectMappingService mappingService;

    @Resource
    private SubjectLabelService labelService;

    //注入我们声明的线程值
    @Resource
    private ThreadPoolExecutor labelThreadPool;

    //谷歌的帮助我们实现的亲,值也是String因为我们可以,我们可以整体的左转换,这个我们抽取到工具类中
    /*
        private Cache<String,String> localCache=
                CacheBuilder.newBuilder()
                        .maximumSize(5000)                                  //内存大小,可以存储5000个键值对来的
                        .expireAfterWrite(10,TimeUnit.SECONDS)      //过期时间
                        .build();
     */

    @Resource
    private CacheUtil cacheUtil;

    @Override
    public void add(SubjectCategoryBO subjectCategoryBO) {
        if (log.isInfoEnabled()){
            log.info("SubjectCategoryDomainServiceImpl.add.bo:{}",subjectCategoryBO);
        }

        //这个就是转换的
        SubjectCategory subjectCategory = SubjectCategoryConverter.INSTANCE.convertBoToCategory(subjectCategoryBO);
        subjectCategory.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());       //未删除,你也可以设置为默认值的

        categoryService.insert(subjectCategory);
    }


    //查询岗位大类
    @Override
    public List<SubjectCategoryBO> queryCategory(SubjectCategoryBO subjectCategoryBO) {
        //组装一个进去,.这样sql就不用写死了
        /*
            SubjectCategory subjectCategory = new SubjectCategory();
            subjectCategory.setParentId(0L);
         */

        //这里直接传对象进去也是非常方便的
        SubjectCategory subjectCategory = SubjectCategoryConverter.INSTANCE.convertBoToCategory(subjectCategoryBO);

        subjectCategory.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());

        List<SubjectCategory> subjectCategoryList=categoryService.queryCategory(subjectCategory);

        List<SubjectCategoryBO> boList = SubjectCategoryConverter.INSTANCE.convertBoToCategory(subjectCategoryList);

        if (log.isInfoEnabled()){
            log.info("SubjectCategoryDomainServiceImpl.add.boList:{}",boList);
        }

        //查询题目总数
        boList.forEach(bo -> {
            Integer subjectCount=categoryService.querySubjectCount(bo.getId());
            bo.setCount(subjectCount);      //每个都设置一下
        });


        return boList;
    }


    //更新分类
    @Override
    public Boolean update(SubjectCategoryBO subjectCategoryBO) {
        SubjectCategory subjectCategory = SubjectCategoryConverter.INSTANCE.convertBoToCategory(subjectCategoryBO);

        int count = categoryService.update(subjectCategory);

        return count>0;
    }



    @Override
    public Boolean delete(SubjectCategoryBO subjectCategoryBO) {
        SubjectCategory subjectCategory = SubjectCategoryConverter.INSTANCE.convertBoToCategory(subjectCategoryBO);

        //逻辑删除
        subjectCategory.setIsDeleted(IsDeletedFlagEnum.DELETED.getCode());

        int count = categoryService.update(subjectCategory);

        return count > 0;
    }



    //一口气查出来所有的分类和标签
    @Override
    public List<SubjectCategoryBO> queryCategoryAndLabel1_0(SubjectCategoryBO subjectCategoryBO) {
        //查询当前大类下的所有小分类
        SubjectCategory subjectCategory = new SubjectCategory();
        subjectCategory.setParentId(subjectCategoryBO.getId());
        subjectCategory.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());

        //所有的小分类亲
        List<SubjectCategory> subjectCategories = categoryService.queryCategory(subjectCategory);
        if(log.isInfoEnabled()){
            log.info("queryCategoryAndLabel.subjectCategories.list:{}", JSON.toJSONString(subjectCategories));
        }

        //再去查询出来所有的小标签的所有标签
        List<SubjectCategoryBO> categoryBOList=SubjectCategoryConverter.INSTANCE.convertEntityToBo(subjectCategories);

        //一次性获取所有的标签信息
        categoryBOList.forEach(category ->{
            /*
                TODO 这里虽然是一次查询的话但是这里的category小分类 也是循环的还是和数据库进行了多次交互的,我们第一版先这样
                     思路1:
                        /用mapping查出来所有的,在内存中去拼装,但是这时非常的麻烦的,节省了数据库连接
                     思路2:
                        /我们不节省数据库连接,但是我去节省时间,这里我们用多线程,分别去查
                            /在组装也是非常好的,我们二期去改
             */
           //我们看看我们的categoryId到底关联那些Mapping,我们只查询和题目有关联的标签
            SubjectMapping subjectMapping = new SubjectMapping();
            subjectMapping.setCategoryId(category.getId());

            List<SubjectMapping> mappingList = mappingService.queryLabelId(subjectMapping);

            if (CollectionUtils.isEmpty(mappingList)){
                return;
            }

            //获得所有的labelId
            List<Long> labelIdlist = mappingList.stream().map(SubjectMapping::getLabelId).collect(Collectors.toList());

            //所有label的信息
            List<SubjectLabel> labelList = labelService.batchQueryById(labelIdlist);

            //组装一下
            List<SubjectLabelBO> labelBOList=new ArrayList<>();

            labelList.forEach(label->{
                SubjectLabelBO subjectLabelBO = new SubjectLabelBO();

                subjectLabelBO.setId(label.getId());
                subjectLabelBO.setLabelName(label.getLabelName());
                subjectLabelBO.setCategoryId(label.getCategoryId());
                subjectLabelBO.setSortNum(label.getSortNum());

                labelBOList.add(subjectLabelBO);
            });

            category.setLabelBOList(labelBOList);

        });


        //返回所有的信息
        return categoryBOList;
    }

    //这个查询我们优化为下面的多线程版本,上面是1.0的版本,异步编程
    @Override
    @SneakyThrows
    public List<SubjectCategoryBO> queryCategoryAndLabel2_0(SubjectCategoryBO subjectCategoryBO) {
        //查询当前大类下的所有小分类
        SubjectCategory subjectCategory = new SubjectCategory();
        subjectCategory.setParentId(subjectCategoryBO.getId());
        subjectCategory.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());

        //所有的小分类亲
        List<SubjectCategory> subjectCategories = categoryService.queryCategory(subjectCategory);
        if(log.isInfoEnabled()){
            log.info("queryCategoryAndLabel.subjectCategories.list:{}", JSON.toJSONString(subjectCategories));
        }

        //再去查询出来所有的小标签的所有标签
        List<SubjectCategoryBO> categoryBOList=SubjectCategoryConverter.INSTANCE.convertEntityToBo(subjectCategories);

        //一次性获取所有的标签信息
        /*
            这里我们得到所有标签之后,我们是无限的forEach,我们这里用多线程做异步的,并发查询
                /用线程池提交任务亲
         */
        //FutureTask的泛型就是把我们想返回的东西拿到之后add进去,但是labelList和category之间的管理就是id关联的亲,我们这里就是存储所的结果
        List<FutureTask<Map<Long,List<SubjectLabelBO>>>> futureTaskList=new ArrayList<>();

        //接受结果用的
        Map<Long,List<SubjectLabelBO>> map=new HashMap<>();

        //线程池的并发调用亲
        categoryBOList.forEach(category ->{
            FutureTask<Map<Long,List<SubjectLabelBO>>> futureTask=new FutureTask<>(new Callable<Map<Long, List<SubjectLabelBO>>>() {
                //这里就是线程执行的核心逻辑
                @Override
                public Map<Long, List<SubjectLabelBO>> call() throws Exception {
                    return getLabelBOList(category);
                }
            });

            //提交任务亲
            labelThreadPool.submit(futureTask);


            //把这个放到我的线程池中做数据存储的
            futureTaskList.add(futureTask);
        });

        //遍历结果亲
        for (FutureTask<Map<Long, List<SubjectLabelBO>>> futureTask : futureTaskList) {
            //拿到我们的map亲,数据都放到这里了
            Map<Long, List<SubjectLabelBO>> resultMap = futureTask.get();

            //如果是空的,什么都不管处理下一个
            if(CollectionUtils.isEmpty(resultMap)){
                continue;
            }

            //不是空的我就往我们的map中方数据
            map.putAll(resultMap);

            /*
                为啥不在这里组装数据,因为这里是所有的结果,我们在resultMap.get的话要去所有结果中查询的
                    /性能不高的
             */
        }

        //组装数据
        categoryBOList.forEach(categoryBO->{
            //这里就是我们为什么传递id来的
            categoryBO.setLabelBOList(map.get(categoryBO.getId()));
        });

        //返回所有的信息
        return categoryBOList;
    }


    //这里我们使用更加新的线程池子,也是非常主流的方式就是,complateTableFutureTask线程池子来的
    @Override
    @SneakyThrows
    public List<SubjectCategoryBO> queryCategoryAndLabel2_1(SubjectCategoryBO subjectCategoryBO) {
        /**
         * 我们这里使用最简单的guava,就是类似我们的caffiein
         */

        //查询当前大类下的所有小分类
        SubjectCategory subjectCategory = new SubjectCategory();
        subjectCategory.setParentId(subjectCategoryBO.getId());
        subjectCategory.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());

        //所有的小分类亲
        List<SubjectCategory> subjectCategories = categoryService.queryCategory(subjectCategory);
        if(log.isInfoEnabled()){
            log.info("queryCategoryAndLabel.subjectCategories.list:{}", JSON.toJSONString(subjectCategories));
        }

        //再去查询出来所有的小标签的所有标签
        List<SubjectCategoryBO> categoryBOList=SubjectCategoryConverter.INSTANCE.convertEntityToBo(subjectCategories);

        //接受结果用的
        Map<Long,List<SubjectLabelBO>> map=new HashMap<>();

        //使用新的线程池
        List<CompletableFuture<Map<Long, List<SubjectLabelBO>>>> completableFutureList = categoryBOList.stream().map(category ->
                CompletableFuture.supplyAsync(() -> getLabelBOList(category), labelThreadPool)  //参数传入我们的线程池即可
        ).collect(Collectors.toList());

        //接受结果
        completableFutureList.forEach(future -> {
            try {
                Map<Long, List<SubjectLabelBO>> resultMap = future.get();
                if (!MapUtils.isEmpty(resultMap)) {
                    map.putAll(resultMap);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        //组装数据
        categoryBOList.forEach(categoryBO -> {
            if (!CollectionUtils.isEmpty(map.get(categoryBO.getId()))) {
                categoryBO.setLabelBOList(map.get(categoryBO.getId()));
            }
        });


        //返回所有的信息
        return categoryBOList;
    }


    //根据category查询所有的标签,给版本2使用的亲
    private Map<Long,List<SubjectLabelBO>> getLabelBOList(SubjectCategoryBO category) {
        if (log.isInfoEnabled()){
            log.info("getLabelBOList",JSON.toJSONString(category));
        }


        Map<Long,List<SubjectLabelBO>> labelMap=new HashMap<>();

        //我们看看我们的categoryId到底关联那些Mapping,我们只查询和题目有关联的标签
        SubjectMapping subjectMapping = new SubjectMapping();
        subjectMapping.setCategoryId(category.getId());

        List<SubjectMapping> mappingList = mappingService.queryLabelId(subjectMapping);

        if (CollectionUtils.isEmpty(mappingList)){
            return null;
        }

        //获得所有的labelId
        List<Long> labelIdlist = mappingList.stream().map(SubjectMapping::getLabelId).collect(Collectors.toList());

        //所有label的信息
        List<SubjectLabel> labelList = labelService.batchQueryById(labelIdlist);

        //组装一下
        List<SubjectLabelBO> labelBOList=new ArrayList<>();

        labelList.forEach(label->{
            SubjectLabelBO subjectLabelBO = new SubjectLabelBO();

            subjectLabelBO.setId(label.getId());
            subjectLabelBO.setLabelName(label.getLabelName());
            subjectLabelBO.setCategoryId(label.getCategoryId());
            subjectLabelBO.setSortNum(label.getSortNum());

            labelBOList.add(subjectLabelBO);
        });

        labelMap.put(category.getId(),labelBOList);

        return labelMap;
    }


    //这里我做本地的缓存亲
    @Override
    @SneakyThrows
    public List<SubjectCategoryBO> queryCategoryAndLabel2_2(SubjectCategoryBO subjectCategoryBO) {
        /**
         * 我们这里使用最简单的guava,就是类似我们的caffiein
         */

        //这里先使用谷歌的方法
        String cacheKey="CategoryAndLabel."+subjectCategoryBO.getId();      //缓存的key
        /*
            String content = localCache.getIfPresent(cacheKey);

            List<SubjectCategoryBO> subjectCategoryBOS=new ArrayList<>();

            //本地缓存是空的我们就去查新,没有的话就去数据库亲
            if(StringUtils.isBlank(content))
            {
                subjectCategoryBOS = getSubjectCategoryBOS(subjectCategoryBO.getId());

                //写入缓存
                localCache.put(cacheKey, JSON.toJSONString(subjectCategoryBOS));

                return subjectCategoryBOS;
            }else
            {
                //缓存不空
                subjectCategoryBOS=JSON.parseArray(content,SubjectCategoryBO.class);
            }
         */

        Long id = subjectCategoryBO.getId();

        List<SubjectCategoryBO> result = cacheUtil.getResult(cacheKey, SubjectCategoryBO.class, (key) -> getSubjectCategoryBOS(id));

        return result;
    }

    //提取的方法,便于做缓存的亲,我们这里传入我们的categoryId即可,查询当前大类下的所有小分类
    private List<SubjectCategoryBO> getSubjectCategoryBOS(Long categoryId) {
        SubjectCategory subjectCategory = new SubjectCategory();
        subjectCategory.setParentId(categoryId);
        subjectCategory.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());

        //所有的小分类亲
        List<SubjectCategory> subjectCategories = categoryService.queryCategory(subjectCategory);
        if(log.isInfoEnabled()){
            log.info("queryCategoryAndLabel.subjectCategories.list:{}", JSON.toJSONString(subjectCategories));
        }

        //再去查询出来所有的小标签的所有标签
        List<SubjectCategoryBO> categoryBOList=SubjectCategoryConverter.INSTANCE.convertEntityToBo(subjectCategories);

        //接受结果用的
        Map<Long,List<SubjectLabelBO>> map=new HashMap<>();

        //使用新的线程池
        List<CompletableFuture<Map<Long, List<SubjectLabelBO>>>> completableFutureList = categoryBOList.stream().map(category ->
                CompletableFuture.supplyAsync(() -> getLabelBOList(category), labelThreadPool)  //参数传入我们的线程池即可
        ).collect(Collectors.toList());

        //接受结果
        completableFutureList.forEach(future -> {
            try {
                Map<Long, List<SubjectLabelBO>> resultMap = future.get();
                if (!MapUtils.isEmpty(resultMap)) {
                    map.putAll(resultMap);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        //组装数据
        categoryBOList.forEach(categoryBO -> {
            if (!CollectionUtils.isEmpty(map.get(categoryBO.getId()))) {
                categoryBO.setLabelBOList(map.get(categoryBO.getId()));
            }
        });


        //返回所有的信息
        return categoryBOList;
    }
}
